Completed
Pull Request — master (#99)
by Ruben de
01:21 queued 20s
created

APIClient._createNewWalletV2   B

Complexity

Conditions 1
Paths 16384

Size

Total Lines 109

Duplication

Lines 108
Ratio 99.08 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 1
c 2
b 1
f 1
nc 16384
dl 108
loc 109
rs 8.2857
nop 1

3 Functions

Rating   Name   Duplication   Size   Complexity  
A 0 1 1
A 14 14 2
A 0 1 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        var rawTxHost = (options.testnet ? 't' : '') + 'chain.btc.com';
128
129
        self.converter = new BtccomConverter(self.network, true, rawTxHost);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    } else {
131
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
132
    }
133
134
};
135
136
APIClient.normalizeNetworkFromOptions = function(options) {
137
    /* jshint -W071, -W074 */
138
    var network = 'BTC';
139
    var testnet = false;
140
    var regtest = false;
141
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
142
143
    var prefix;
144
    var done = false;
145
146
    if (options.network) {
147
        var lower = options.network.toLowerCase();
148
149
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
150
        if (!m) {
151
            throw new Error("Invalid network [" + options.network + "]");
152
        }
153
154
        if (m[2] === 'btc') {
155
            network = "BTC";
156
        } else {
157
            network = "BCC";
158
        }
159
160
        prefix = m[1];
161
        if (prefix) {
162
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
163
            done = true;
164
            if (prefix === 'r') {
165
                testnet = true;
166
                regtest = true;
167
            } else if (prefix === 't') {
168
                testnet = true;
169
            }
170
        }
171
    }
172
173
    // if we're not already done then apply options.regtest and options.testnet
174
    if (!done) {
175
        if (options.regtest) {
176
            testnet = true;
177
            regtest = true;
178
            prefix = "r";
179
        } else if (options.testnet) {
180
            testnet = true;
181
            prefix = "t";
182
        }
183
    }
184
185
    apiNetwork = (prefix || "") + network;
186
187
    return [network, testnet, regtest, apiNetwork];
188
};
189
190
APIClient.updateHostOptions = function(options) {
191
    /* jshint -W071, -W074 */
192
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
193
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
194
        options.host = options.btccom ? process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT : process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
195
    }
196
197
    // trim off leading https?://
198
    if (options.host && options.host.indexOf("https://") === 0) {
199
        options.https = true;
200
        options.host = options.host.substr(8);
201
    } else if (options.host && options.host.indexOf("http://") === 0) {
202
        options.https = false;
203
        options.host = options.host.substr(7);
204
    }
205
206
    if (typeof options.https === "undefined") {
207
        options.https = true;
208
    }
209
210
    if (!options.port) {
211
        options.port = options.https ? 443 : 80;
212
    }
213
214
    if (options.btccom) {
215
        if (!options.host) {
216
            options.host = 'chain.api.btc.com';
217
        }
218
219
        if (options.testnet && !options.host.match(/tchain/)) {
220
            options.host = options.host.replace(/chain/, 'tchain');
221
        }
222
223
        if (!options.endpoint) {
224
            options.endpoint = "/" + (options.apiVersion || "v3");
225
        }
226
    } else {
227
        if (!options.host) {
228
            options.host = 'api.blocktrail.com';
229
        }
230
231
        if (!options.endpoint) {
232
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
233
        }
234
    }
235
236
    return options;
237
};
238
239
APIClient.initRestClient = function(options) {
240
    options = APIClient.updateHostOptions(options);
241
    return new RestClient(options);
242
};
243
244
var determineDataStorageV2_3 = function(options) {
245
    return q.when(options)
246
        .then(function(options) {
247
            // legacy
248
            if (options.storePrimaryMnemonic) {
249
                options.storeDataOnServer = options.storePrimaryMnemonic;
250
            }
251
252
            // storeDataOnServer=false when primarySeed is provided
253
            if (typeof options.storeDataOnServer === "undefined") {
254
                options.storeDataOnServer = !options.primarySeed;
255
            }
256
257
            return options;
258
        });
259
};
260
261
var produceEncryptedDataV2 = function(options, notify) {
262
    return q.when(options)
263
        .then(function(options) {
264
            if (options.storeDataOnServer) {
265
                if (!options.secret) {
266
                    if (!options.passphrase) {
267
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
268
                    }
269
270
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
271
272
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
273
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
274
                }
275
276
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
277
278
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
279
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
280
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
281
282
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
283
284
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
285
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
286
            }
287
288
            return options;
289
        });
290
};
291
292
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
293
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
294
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
295
        var saltBuf = Encryption.generateSalt();
296
        var iv = Encryption.generateIV();
297
298
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
299
            return require('./webworker');
300
        }, onLoadWorkerLoadAsmCrypto, {
301
            method: 'Encryption.encryptWithSaltAndIV',
302
            pt: pt,
303
            pw: pw,
304
            saltBuf: saltBuf,
305
            iv: iv,
306
            iterations: iter
307
        })
308
            .then(function(data) {
309
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
310
            });
311
    } else {
312
        try {
313
            return q.when(Encryption.encrypt(pt, pw, iter));
314
        } catch (e) {
315
            return q.reject(e);
316
        }
317
    }
318
};
319
320
APIClient.prototype.promisedDecrypt = function(ct, pw) {
321
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
322
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
323
            return require('./webworker');
324
        }, onLoadWorkerLoadAsmCrypto, {
325
            method: 'Encryption.decrypt',
326
            ct: ct,
327
            pw: pw
328
        })
329
            .then(function(data) {
330
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
331
            });
332
    } else {
333
        try {
334
            return q.when(Encryption.decrypt(ct, pw));
335
        } catch (e) {
336
            return q.reject(e);
337
        }
338
    }
339
};
340
341
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
342
    var self = this;
343
344
    return q.when(options)
345
        .then(function(options) {
346
            if (options.storeDataOnServer) {
347
                return q.when()
348
                    .then(function() {
349
                        if (!options.secret) {
350
                            if (!options.passphrase) {
351
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
352
                            }
353
354
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
355
356
                            // -> now a buffer
357
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
358
359
                            // -> now a buffer
360
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
361
                                .then(function(encryptedSecret) {
362
                                    options.encryptedSecret = encryptedSecret;
363
                                });
364
                        } else {
365
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
366
                                throw new Error('Secret must be a buffer');
367
                            }
368
                        }
369
                    })
370
                    .then(function() {
371
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
372
373
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
374
                            .then(function(encryptedPrimarySeed) {
375
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
376
                            });
377
                    })
378
                    .then(function() {
379
                        // skip generating recovery secret when explicitly set to false
380
                        if (options.recoverySecret === false) {
381
                            return;
382
                        }
383
384
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
385
                        if (!options.recoverySecret) {
386
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
387
                        }
388
389
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
390
                            .then(function(recoveryEncryptedSecret) {
391
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
392
                            });
393
                    })
394
                    .then(function() {
395
                        return options;
396
                    });
397
            } else {
398
                return options;
399
            }
400
        });
401
};
402
403
var doRemainingWalletDataV2_3 = function(options, network, notify) {
404
    return q.when(options)
405
        .then(function(options) {
406
            if (!options.backupPublicKey) {
407
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
408
            }
409
410
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
411
412
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
413
414
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
415
416
            if (!options.backupPublicKey) {
417
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
418
                options.backupPublicKey = options.backupPrivateKey.neutered();
419
            }
420
421
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
422
423
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
424
425
            return options;
426
        });
427
};
428
429
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
430
    var self = this;
431
432
    var deferred = q.defer();
433
    deferred.promise.spreadNodeify(cb);
434
435
    deferred.resolve(q.fcall(function() {
436
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
437
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
438
        });
439
    }));
440
441
    return deferred.promise;
442
};
443
444
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
445
    var self = this;
446
447
    if (useWebWorker) {
448
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
449
            return require('./webworker');
450
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
451
            .then(function(data) {
452
                return data.seed;
453
            });
454
    } else {
455
        try {
456
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
457
        } catch (e) {
458
            return q.reject(e);
459
        }
460
    }
461
};
462
463
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
464
    var self = this;
465
466
    var deferred = q.defer();
467
    deferred.promise.nodeify(cb);
468
469
    try {
470
        // avoid conflicting options
471
        if (options.passphrase && options.password) {
472
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
473
        }
474
        // normalize passphrase/password
475
        options.passphrase = options.passphrase || options.password;
476
        delete options.password;
477
478
        // avoid conflicting options
479
        if (options.primaryMnemonic && options.primarySeed) {
480
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
481
        }
482
483
        // avoid deprecated options
484
        if (options.primaryPrivateKey) {
485
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
486
        }
487
488
        // make sure we have at least one thing to use
489
        if (!options.primaryMnemonic && !options.primarySeed) {
490
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
491
        }
492
493
        if (options.primarySeed) {
494
            self.primarySeed = options.primarySeed;
495
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
496
            deferred.resolve(options);
497
        } else {
498
            if (!options.passphrase) {
499
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
500
            }
501
502
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
503
                .then(function(seedHex) {
504
                    try {
505
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
506
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
507
                        deferred.resolve(options);
508
                    } catch (e) {
509
                        deferred.reject(e);
510
                    }
511
                }, function(e) {
512
                    deferred.reject(e);
513
                });
514
        }
515
    } catch (e) {
516
        deferred.reject(e);
517
    }
518
519
    return deferred.promise;
520
};
521
522
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
523
    var self = this;
524
525
    var deferred = q.defer();
526
    deferred.promise.nodeify(cb);
527
528
    try {
529
        // avoid conflicting options
530
        if (options.backupMnemonic && options.backupPublicKey) {
531
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
532
        }
533
534
        // make sure we have at least one thing to use
535
        if (!options.backupMnemonic && !options.backupPublicKey) {
536
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
537
        }
538
539
        if (options.backupPublicKey) {
540
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
541
                deferred.resolve(options);
542
            } else {
543
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
544
                deferred.resolve(options);
545
            }
546
        } else {
547
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
548
                options.backupPublicKey = backupPrivateKey.neutered();
549
                deferred.resolve(options);
550
            }, function(e) {
551
                deferred.reject(e);
552
            });
553
        }
554
    } catch (e) {
555
        deferred.reject(e);
556
    }
557
558
    return deferred.promise;
559
};
560
561
APIClient.prototype.debugAuth = function(cb) {
562
    var self = this;
563
564
    return self.dataClient.get("/debug/http-signature", null, true, cb);
565
};
566
567
/**
568
 * get a single address
569
 *
570
 * @param address      string       address hash
571
 * @param [cb]          function    callback function to call when request is complete
572
 * @return q.Promise
573
 */
574
APIClient.prototype.address = function(address, cb) {
575
    var self = this;
576
577
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
578
        .then(function(data) {
579
            return self.converter.handleErros(self, data);
580
        })
581
        .then(function(data) {
582
            if (data === null) {
583
                return data;
584
            } else {
585
                return self.converter.convertAddress(data);
586
            }
587
        }), cb);
588
};
589
590
APIClient.prototype.addresses = function(addresses, cb) {
591
    var self = this;
592
593
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
594
};
595
596
597
/**
598
 * get all transactions for an address (paginated)
599
 *
600
 * @param address       string      address hash
601
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
602
 * @param [cb]          function    callback function to call when request is complete
603
 * @return q.Promise
604
 */
605
APIClient.prototype.addressTransactions = function(address, params, cb) {
606
607
    var self = this;
608
609
    if (typeof params === "function") {
610
        cb = params;
611
        params = null;
612
    }
613
614
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
615
        .then(function(data) {
616
            return self.converter.handleErros(self, data);
617
        })
618
        .then(function(data) {
619
            return data.data === null ? data : self.converter.convertAddressTxs(data);
620
        }), cb);
621
};
622
623
/**
624
 * get all transactions for a batch of addresses (paginated)
625
 *
626
 * @param addresses     array       address hashes
627
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
628
 * @param [cb]          function    callback function to call when request is complete
629
 * @return q.Promise
630
 */
631
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
632
    var self = this;
633
634
    if (typeof params === "function") {
635
        cb = params;
636
        params = null;
637
    }
638
639
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
640
};
641
642
/**
643
 * get all unconfirmed transactions for an address (paginated)
644
 *
645
 * @param address       string      address hash
646
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
647
 * @param [cb]          function    callback function to call when request is complete
648
 * @return q.Promise
649
 */
650
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
651
    var self = this;
652
653
    if (typeof params === "function") {
654
        cb = params;
655
        params = null;
656
    }
657
658
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
659
        .then(function(data) {
660
            return self.converter.handleErros(self, data);
661
        })
662
        .then(function(data) {
663
            if (data.data === null) {
664
                return data;
665
            }
666
667
            var res = self.converter.convertAddressTxs(data);
668
            res.data = res.data.filter(function(tx) {
669
                return !tx.confirmations;
670
            });
671
672
            return res;
673
        }), cb);
674
};
675
676
/**
677
 * get all unspent outputs for an address (paginated)
678
 *
679
 * @param address       string      address hash
680
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
681
 * @param [cb]          function    callback function to call when request is complete
682
 * @return q.Promise
683
 */
684
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
685
    var self = this;
686
687
    if (typeof params === "function") {
688
        cb = params;
689
        params = null;
690
    }
691
692
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
693
        .then(function(data) {
694
            return self.converter.handleErros(self, data);
695
        })
696
        .then(function(data) {
697
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
698
        }), cb);
699
};
700
701
/**
702
 * get all unspent outputs for a batch of addresses (paginated)
703
 *
704
 * @param addresses     array       address hashes
705
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
706
 * @param [cb]          function    callback function to call when request is complete
707
 * @return q.Promise
708
 */
709
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
710
    var self = this;
711
712
    if (self.converter instanceof BtccomConverter) {
713
        throw new Error("Not implemented");
714
    }
715
716
    if (typeof params === "function") {
717
        cb = params;
718
        params = null;
719
    }
720
721
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
722
};
723
724
/**
725
 * verify ownership of an address
726
 *
727
 * @param address       string      address hash
728
 * @param signature     string      a signed message (the address hash) using the private key of the address
729
 * @param [cb]          function    callback function to call when request is complete
730
 * @return q.Promise
731
 */
732
APIClient.prototype.verifyAddress = function(address, signature, cb) {
733
    var self = this;
734
735
    return self.verifyMessage(address, address, signature, cb);
736
};
737
738
/**
739
 *
740
 * get all blocks (paginated)
741
 * ASK
742
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
743
 * @param [cb]          function    callback function to call when request is complete
744
 * @return q.Promise
745
 */
746
APIClient.prototype.allBlocks = function(params, cb) {
747
    var self = this;
748
749
    if (self.converter instanceof BtccomConverter) {
750
        throw new Error("Not implemented");
751
    }
752
753
    if (typeof params === "function") {
754
        cb = params;
755
        params = null;
756
    }
757
758
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
759
};
760
761
/**
762
 * get a block
763
 *
764
 * @param block         string|int  a block hash or a block height
765
 * @param [cb]          function    callback function to call when request is complete
766
 * @return q.Promise
767
 */
768
APIClient.prototype.block = function(block, cb) {
769
    var self = this;
770
771
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
772
        .then(function(data) {
773
            return self.converter.handleErros(self, data);
774
        })
775
        .then(function(data) {
776
            return data.data === null ? data : self.converter.convertBlock(data);
777
        }), cb);
778
};
779
780
/**
781
 * get the latest block
782
 *
783
 * @param [cb]          function    callback function to call when request is complete
784
 * @return q.Promise
785
 */
786
APIClient.prototype.blockLatest = function(cb) {
787
    var self = this;
788
789
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
790
        .then(function(data) {
791
            return self.converter.handleErros(self, data);
792
        })
793
        .then(function(data) {
794
            return data.data === null ? data : self.converter.convertBlock(data);
795
        }), cb);
796
};
797
798
/**
799
 * get all transactions for a block (paginated)
800
 *
801
 * @param block         string|int  a block hash or a block height
802
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
803
 * @param [cb]          function    callback function to call when request is complete
804
 * @return q.Promise
805
 */
806
APIClient.prototype.blockTransactions = function(block, params, cb) {
807
    var self = this;
808
809
    if (typeof params === "function") {
810
        cb = params;
811
        params = null;
812
    }
813
814
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
815
        .then(function(data) {
816
            return self.converter.handleErros(self, data);
817
        })
818
        .then(function(data) {
819
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
820
        }), cb);
821
};
822
823
/**
824
 * get a single transaction
825
 *
826
 * @param tx            string      transaction hash
827
 * @param [cb]          function    callback function to call when request is complete
828
 * @return q.Promise
829
 */
830
APIClient.prototype.transaction = function(tx, cb) {
831
    var self = this;
832
833
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
834
        .then(function(data) {
835
            return self.converter.handleErros(self, data);
836
        })
837
        .then(function(data) {
838
            if (data.data === null) {
839
                return data;
840
            } else {
841
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
842
                if (self.converter instanceof BtccomConverter) {
843
                    var txPath = data.data.hash + ".rawhex";
844
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
845
                        .then(function(rawTx) {
846
                            return [data, rawTx];
847
                        })
848
                        .then(function(dataAndTx) {
849
                            if (dataAndTx !== null) {
850
                                var data = dataAndTx[0];
851
                                var rawTx = dataAndTx[1];
852
                                return self.converter.convertTx(data, rawTx);
853
                            } else {
854
                                return dataAndTx;
855
                            }
856
                        });
857
                } else {
858
                    return self.converter.convertTx(data);
859
                }
860
            }
861
        }), cb);
862
};
863
864
/**
865
 * get a batch of transactions
866
 *
867
 * @param txs           string[]    list of transaction hashes (txId)
868
 * @param [cb]          function    callback function to call when request is complete
869
 * @return q.Promise
870
 */
871
APIClient.prototype.transactions = function(txs, cb) {
872
    var self = this;
873
874
    if (self.converter instanceof BtccomConverter) {
875
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
876
            .then(function(data) {
877
                return self.converter.handleErros(self, data);
878
            })
879
            .then(function(data) {
880
                if (data.data === null) {
881
                    return data;
882
                } else {
883
                    return self.converter.convertTxs(data);
884
                }
885
            }), cb);
886
    } else {
887
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
888
    }
889
};
890
891
/**
892
 * get a paginated list of all webhooks associated with the api user
893
 *
894
 * @param [params]      object      pagination: {page: 1, limit: 20}
895
 * @param [cb]          function    callback function to call when request is complete
896
 * @return q.Promise
897
 */
898
APIClient.prototype.allWebhooks = function(params, cb) {
899
    var self = this;
900
901
    if (typeof params === "function") {
902
        cb = params;
903
        params = null;
904
    }
905
906
    return self.blocktrailClient.get("/webhooks", params, cb);
907
};
908
909
/**
910
 * create a new webhook
911
 *
912
 * @param url           string      the url to receive the webhook events
913
 * @param [identifier]  string      a unique identifier associated with the webhook
914
 * @param [cb]          function    callback function to call when request is complete
915
 * @return q.Promise
916
 */
917
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
918
    var self = this;
919
920
    if (typeof identifier === "function") {
921
        //mimic function overloading
922
        cb = identifier;
923
        identifier = null;
924
    }
925
926
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
927
};
928
929
/**
930
 * Converts a cash address to the legacy (base58) format
931
 * @param {string} input
932
 * @returns {string}
933
 */
934
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
935
    if (this.network === bitcoin.networks.bitcoincash ||
936
        this.network === bitcoin.networks.bitcoincashtestnet ||
937
        this.network === bitcoin.networks.bitcoincashregtest) {
938
        var address;
939
        try {
940
            bitcoin.address.fromBase58Check(input, this.network);
941
            return input;
942
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
943
944
        address = bitcoin.address.fromCashAddress(input, this.network);
945
        var prefix;
946
        if (address.version === bitcoin.script.types.P2PKH) {
947
            prefix = this.network.pubKeyHash;
948
        } else if (address.version === bitcoin.script.types.P2SH) {
949
            prefix = this.network.scriptHash;
950
        } else {
951
            throw new Error("Unsupported address type");
952
        }
953
954
        return bitcoin.address.toBase58Check(address.hash, prefix);
955
    }
956
957
    throw new Error("Cash addresses only work on bitcoin cash");
958
};
959
960
/**
961
 * Converts a legacy bitcoin to the new cashaddr format
962
 * @param {string} input
963
 * @returns {string}
964
 */
965
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
966
    if (this.network === bitcoin.networks.bitcoincash ||
967
        this.network === bitcoin.networks.bitcoincashtestnet ||
968
        this.network === bitcoin.networks.bitcoincashregtest
969
    ) {
970
        var address;
971
        try {
972
            bitcoin.address.fromCashAddress(input, this.network);
973
            return input;
974
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
975
976
        address = bitcoin.address.fromBase58Check(input, this.network);
977
        var scriptType;
978
        if (address.version === this.network.pubKeyHash) {
979
            scriptType = bitcoin.script.types.P2PKH;
980
        } else if (address.version === this.network.scriptHash) {
981
            scriptType = bitcoin.script.types.P2SH;
982
        } else {
983
            throw new Error("Unsupported address type");
984
        }
985
986
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
987
    }
988
989
    throw new Error("Cash addresses only work on bitcoin cash");
990
};
991
992
/**
993
 * get an existing webhook by it's identifier
994
 *
995
 * @param identifier    string      the unique identifier of the webhook to get
996
 * @param [cb]          function    callback function to call when request is complete
997
 * @return q.Promise
998
 */
999
APIClient.prototype.getWebhook = function(identifier, cb) {
1000
    var self = this;
1001
1002
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
1003
};
1004
1005
/**
1006
 * update an existing webhook
1007
 *
1008
 * @param identifier    string      the unique identifier of the webhook
1009
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
1010
 * @param [cb]          function    callback function to call when request is complete
1011
 * @return q.Promise
1012
 */
1013
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1014
    var self = this;
1015
1016
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1017
};
1018
1019
/**
1020
 * deletes an existing webhook and any event subscriptions associated with it
1021
 *
1022
 * @param identifier    string      the unique identifier of the webhook
1023
 * @param [cb]          function    callback function to call when request is complete
1024
 * @return q.Promise
1025
 */
1026
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1027
    var self = this;
1028
1029
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1030
};
1031
1032
/**
1033
 * get a paginated list of all the events a webhook is subscribed to
1034
 *
1035
 * @param identifier    string      the unique identifier of the webhook
1036
 * @param [params]      object      pagination: {page: 1, limit: 20}
1037
 * @param [cb]          function    callback function to call when request is complete
1038
 * @return q.Promise
1039
 */
1040
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1041
    var self = this;
1042
1043
    if (typeof params === "function") {
1044
        cb = params;
1045
        params = null;
1046
    }
1047
1048
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1049
};
1050
1051
/**
1052
 * subscribes a webhook to transaction events for a particular transaction
1053
 *
1054
 * @param identifier    string      the unique identifier of the webhook
1055
 * @param transaction   string      the transaction hash
1056
 * @param confirmations integer     the amount of confirmations to send
1057
 * @param [cb]          function    callback function to call when request is complete
1058
 * @return q.Promise
1059
 */
1060
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1061
    var self = this;
1062
    var postData = {
1063
        'event_type': 'transaction',
1064
        'transaction': transaction,
1065
        'confirmations': confirmations
1066
    };
1067
1068
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1069
};
1070
1071
/**
1072
 * subscribes a webhook to transaction events on a particular address
1073
 *
1074
 * @param identifier    string      the unique identifier of the webhook
1075
 * @param address       string      the address hash
1076
 * @param confirmations integer     the amount of confirmations to send
1077
 * @param [cb]          function    callback function to call when request is complete
1078
 * @return q.Promise
1079
 */
1080
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1081
    var self = this;
1082
    var postData = {
1083
        'event_type': 'address-transactions',
1084
        'address': address,
1085
        'confirmations': confirmations
1086
    };
1087
1088
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1089
};
1090
1091
/**
1092
 * batch subscribes a webhook to multiple transaction events
1093
 *
1094
 * @param  identifier   string      the unique identifier of the webhook
1095
 * @param  batchData    array       An array of objects containing batch event data:
1096
 *                                  {address : 'address', confirmations : 'confirmations']
1097
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1098
 * @param [cb]          function    callback function to call when request is complete
1099
 * @return q.Promise
1100
 */
1101
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1102
    var self = this;
1103
    batchData.forEach(function(record) {
1104
        record.event_type = 'address-transactions';
1105
    });
1106
1107
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1108
};
1109
1110
/**
1111
 * subscribes a webhook to a new block event
1112
 *
1113
 * @param identifier    string      the unique identifier of the webhook
1114
 * @param [cb]          function    callback function to call when request is complete
1115
 * @return q.Promise
1116
 */
1117
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1118
    var self = this;
1119
    var postData = {
1120
        'event_type': 'block'
1121
    };
1122
1123
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1124
};
1125
1126
/**
1127
 * removes an address transaction event subscription from a webhook
1128
 *
1129
 * @param identifier    string      the unique identifier of the webhook
1130
 * @param address       string      the address hash
1131
 * @param [cb]          function    callback function to call when request is complete
1132
 * @return q.Promise
1133
 */
1134
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1135
    var self = this;
1136
1137
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1138
};
1139
1140
/**
1141
 * removes an transaction event subscription from a webhook
1142
 *
1143
 * @param identifier    string      the unique identifier of the webhook
1144
 * @param transaction   string      the transaction hash
1145
 * @param [cb]          function    callback function to call when request is complete
1146
 * @return q.Promise
1147
 */
1148
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1149
    var self = this;
1150
1151
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1152
};
1153
1154
/**
1155
 * removes a block event subscription from a webhook
1156
 *
1157
 * @param identifier    string      the unique identifier of the webhook
1158
 * @param [cb]          function    callback function to call when request is complete
1159
 * @return q.Promise
1160
 */
1161
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1162
    var self = this;
1163
1164
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1165
};
1166
1167
/**
1168
 * initialize an existing wallet
1169
 *
1170
 * Either takes two argument:
1171
 * @param options       object      {}
1172
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1173
 *
1174
 * Or takes three arguments (old, deprecated syntax):
1175
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1176
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1177
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1172. The second definition is ignored.
Loading history...
1178
 *
1179
 * @returns {q.Promise}
1180
 */
1181
APIClient.prototype.initWallet = function(options, cb) {
1182
    var self = this;
1183
1184
    if (typeof options !== "object") {
1185
        // get the old-style arguments
1186
        options = {
1187
            identifier: arguments[0],
1188
            passphrase: arguments[1]
1189
        };
1190
1191
        cb = arguments[2];
1192
    }
1193
1194
    if (options.check_backup_key) {
1195
        if (typeof options.check_backup_key !== "string") {
1196
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1197
        }
1198
    }
1199
1200
    var deferred = q.defer();
1201
    deferred.promise.spreadNodeify(cb);
1202
1203
    var identifier = options.identifier;
1204
1205
    if (!identifier) {
1206
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1207
        return deferred.promise;
1208
    }
1209
1210
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1211
        var keyIndex = options.keyIndex || result.key_index;
1212
1213
        options.walletVersion = result.wallet_version;
1214
1215
        if (options.check_backup_key) {
1216
            if (options.check_backup_key !== result.backup_public_key[0]) {
1217
                throw new Error("Backup key returned from server didn't match our own copy");
1218
            }
1219
        }
1220
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1221
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1222
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1223
        });
1224
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1225
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1226
        });
1227
1228
        // initialize wallet
1229
        var wallet = new Wallet(
1230
            self,
1231
            identifier,
1232
            options.walletVersion,
1233
            result.primary_mnemonic,
1234
            result.encrypted_primary_seed,
1235
            result.encrypted_secret,
1236
            primaryPublicKeys,
1237
            backupPublicKey,
1238
            blocktrailPublicKeys,
1239
            keyIndex,
1240
            result.segwit || 0,
1241
            self.testnet,
1242
            self.regtest,
1243
            result.checksum,
1244
            result.upgrade_key_index,
1245
            options.useCashAddress,
1246
            options.bypassNewAddressCheck
1247
        );
1248
1249
        wallet.recoverySecret = result.recovery_secret;
1250
1251
        if (!options.readOnly) {
1252
            return wallet.unlock(options).then(function() {
1253
                return wallet;
1254
            });
1255
        } else {
1256
            return wallet;
1257
        }
1258
    }));
1259
1260
    return deferred.promise;
1261
};
1262
1263
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1264
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1265
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1266
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1267
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1268
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1269
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1270
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1271
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1272
1273
/**
1274
 * create a new wallet
1275
 *   - will generate a new primary seed and backup seed
1276
 *
1277
 * Either takes two argument:
1278
 * @param options       object      {}
1279
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1280
 *
1281
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1282
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1278. The second definition is ignored.
Loading history...
1283
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1279. The second definition is ignored.
Loading history...
1284
 *
1285
 * Or takes four arguments (old, deprecated syntax):
1286
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1287
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1288
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1289
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1279. The second definition is ignored.
Loading history...
1290
 * @returns {q.Promise}
1291
 */
1292
APIClient.prototype.createNewWallet = function(options, cb) {
1293
    /* jshint -W071, -W074 */
1294
1295
    var self = this;
1296
1297
    if (typeof options !== "object") {
1298
        // get the old-style arguments
1299
        var identifier = arguments[0];
1300
        var passphrase = arguments[1];
1301
        var keyIndex = arguments[2];
1302
        cb = arguments[3];
1303
1304
        // keyIndex is optional
1305
        if (typeof keyIndex === "function") {
1306
            cb = keyIndex;
1307
            keyIndex = null;
1308
        }
1309
1310
        options = {
1311
            identifier: identifier,
1312
            passphrase: passphrase,
1313
            keyIndex: keyIndex
1314
        };
1315
    }
1316
1317
    // default to v3
1318
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1319
1320
    var deferred = q.defer();
1321
    deferred.promise.spreadNodeify(cb);
1322
1323
    q.nextTick(function() {
1324
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1325
1326
        options.keyIndex = options.keyIndex || 0;
1327
        options.passphrase = options.passphrase || options.password;
1328
        delete options.password;
1329
1330
        if (!options.identifier) {
1331
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1332
            return deferred.promise;
1333
        }
1334
1335
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1336
            self._createNewWalletV1(options)
1337
                .progress(function(p) { deferred.notify(p); })
1338
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1339
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1340
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1341
            self._createNewWalletV2(options)
1342
                .progress(function(p) { deferred.notify(p); })
1343
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1344
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1345
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1346
            self._createNewWalletV3(options)
1347
                .progress(function(p) { deferred.notify(p); })
1348
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1349
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1350
        } else {
1351
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1352
        }
1353
    });
1354
1355
    return deferred.promise;
1356
};
1357
1358
APIClient.prototype._createNewWalletV1 = function(options) {
1359
    var self = this;
1360
1361
    var deferred = q.defer();
1362
1363
    q.nextTick(function() {
1364
1365
        if (!options.primaryMnemonic && !options.primarySeed) {
1366
            if (!options.passphrase && !options.password) {
1367
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1368
                return deferred.promise;
1369
            } else {
1370
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1371
                if (options.storePrimaryMnemonic !== false) {
1372
                    options.storePrimaryMnemonic = true;
1373
                }
1374
            }
1375
        }
1376
1377
        if (!options.backupMnemonic && !options.backupPublicKey) {
1378
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1379
        }
1380
1381
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1382
1383
        self.resolvePrimaryPrivateKeyFromOptions(options)
1384
            .then(function(options) {
1385
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1386
1387
                return self.resolveBackupPublicKeyFromOptions(options)
1388
                    .then(function(options) {
1389
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1390
1391
                        // create a checksum of our private key which we'll later use to verify we used the right password
1392
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1393
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1394
                        var keyIndex = options.keyIndex;
1395
1396
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1397
1398
                        // send the public keys to the server to store them
1399
                        //  and the mnemonic, which is safe because it's useless without the password
1400
                        return self.storeNewWalletV1(
1401
                            options.identifier,
1402
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1403
                            [options.backupPublicKey.toBase58(), "M"],
1404
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1405
                            checksum,
1406
                            keyIndex,
1407
                            options.segwit || null
1408
                        )
1409
                            .then(function(result) {
1410
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1411
1412
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1413
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1414
                                });
1415
1416
                                var wallet = new Wallet(
1417
                                    self,
1418
                                    options.identifier,
1419
                                    Wallet.WALLET_VERSION_V1,
1420
                                    options.primaryMnemonic,
1421
                                    null,
1422
                                    null,
1423
                                    {keyIndex: primaryPublicKey},
1424
                                    options.backupPublicKey,
1425
                                    blocktrailPublicKeys,
1426
                                    keyIndex,
1427
                                    result.segwit || 0,
1428
                                    self.testnet,
1429
                                    self.regtest,
1430
                                    checksum,
1431
                                    result.upgrade_key_index,
1432
                                    options.useCashAddress,
1433
                                    options.bypassNewAddressCheck
1434
                                );
1435
1436
                                return wallet.unlock({
1437
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1438
                                    passphrase: options.passphrase,
1439
                                    primarySeed: options.primarySeed,
1440
                                    primaryMnemonic: null // explicit null
1441
                                }).then(function() {
1442
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1443
                                    return [
1444
                                        wallet,
1445
                                        {
1446
                                            walletVersion: wallet.walletVersion,
1447
                                            primaryMnemonic: options.primaryMnemonic,
1448
                                            backupMnemonic: options.backupMnemonic,
1449
                                            blocktrailPublicKeys: blocktrailPublicKeys
1450
                                        }
1451
                                    ];
1452
                                });
1453
                            });
1454
                    }
1455
                );
1456
            })
1457
            .then(
1458
            function(r) {
1459
                deferred.resolve(r);
1460
            },
1461
            function(e) {
1462
                deferred.reject(e);
1463
            }
1464
        )
1465
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1466
    });
1467 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1468
    return deferred.promise;
1469
};
1470
1471
APIClient.prototype._createNewWalletV2 = function(options) {
1472
    var self = this;
1473
1474
    var deferred = q.defer();
1475
1476
    // avoid modifying passed options
1477
    options = _.merge({}, options);
1478
1479
    determineDataStorageV2_3(options)
1480
        .then(function(options) {
1481
            options.passphrase = options.passphrase || options.password;
1482
            delete options.password;
1483
1484
            // avoid deprecated options
1485
            if (options.primaryPrivateKey) {
1486
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1487
            }
1488
1489
            // seed should be provided or generated
1490
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1491
1492
            return options;
1493
        })
1494
        .then(function(options) {
1495
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1496
        })
1497
        .then(function(options) {
1498
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1499
        })
1500
        .then(function(options) {
1501
            // create a checksum of our private key which we'll later use to verify we used the right password
1502
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1503
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1504
            var keyIndex = options.keyIndex;
1505
1506
            // send the public keys and encrypted data to server
1507
            return self.storeNewWalletV2(
1508
                options.identifier,
1509
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1510
                [options.backupPublicKey.toBase58(), "M"],
1511
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1512
                options.storeDataOnServer ? options.encryptedSecret : false,
1513
                options.storeDataOnServer ? options.recoverySecret : false,
1514
                checksum,
1515
                keyIndex,
1516
                options.support_secret || null,
1517
                options.segwit || null
1518
            )
1519
                .then(
1520
                function(result) {
1521
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1522
1523
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1524
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1525
                    });
1526
1527
                    var wallet = new Wallet(
1528
                        self,
1529
                        options.identifier,
1530
                        Wallet.WALLET_VERSION_V2,
1531
                        null,
1532
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1533
                        options.storeDataOnServer ? options.encryptedSecret : null,
1534
                        {keyIndex: options.primaryPublicKey},
1535
                        options.backupPublicKey,
1536
                        blocktrailPublicKeys,
1537
                        keyIndex,
1538
                        result.segwit || 0,
1539
                        self.testnet,
1540
                        self.regtest,
1541
                        checksum,
1542
                        result.upgrade_key_index,
1543
                        options.useCashAddress,
1544
                        options.bypassNewAddressCheck
1545
                    );
1546
1547
                    // pass along decrypted data to avoid extra work
1548
                    return wallet.unlock({
1549
                        walletVersion: Wallet.WALLET_VERSION_V2,
1550
                        passphrase: options.passphrase,
1551
                        primarySeed: options.primarySeed,
1552
                        secret: options.secret
1553
                    }).then(function() {
1554
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1555
                        return [
1556
                            wallet,
1557
                            {
1558
                                walletVersion: wallet.walletVersion,
1559
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1560
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1561
                                    null,
1562
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1563
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1564
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1565
                                    null,
1566
                                encryptedSecret: options.encryptedSecret ?
1567
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1568
                                    null,
1569
                                blocktrailPublicKeys: blocktrailPublicKeys
1570
                            }
1571
                        ];
1572
                    });
1573
                }
1574
            );
1575
        })
1576
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1577 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1578
    return deferred.promise;
1579
};
1580
1581
APIClient.prototype._createNewWalletV3 = function(options) {
1582
    var self = this;
1583
1584
    var deferred = q.defer();
1585
1586
    // avoid modifying passed options
1587
    options = _.merge({}, options);
1588
1589
    determineDataStorageV2_3(options)
1590
        .then(function(options) {
1591
            options.passphrase = options.passphrase || options.password;
1592
            delete options.password;
1593
1594
            // avoid deprecated options
1595
            if (options.primaryPrivateKey) {
1596
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1597
            }
1598
1599
            // seed should be provided or generated
1600
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1601
1602
            return options;
1603
        })
1604
        .then(function(options) {
1605
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1606
        })
1607
        .then(function(options) {
1608
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1609
        })
1610
        .then(function(options) {
1611
            // create a checksum of our private key which we'll later use to verify we used the right password
1612
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1613
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1614
            var keyIndex = options.keyIndex;
1615
1616
            // send the public keys and encrypted data to server
1617
            return self.storeNewWalletV3(
1618
                options.identifier,
1619
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1620
                [options.backupPublicKey.toBase58(), "M"],
1621
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1622
                options.storeDataOnServer ? options.encryptedSecret : false,
1623
                options.storeDataOnServer ? options.recoverySecret : false,
1624
                checksum,
1625
                keyIndex,
1626
                options.support_secret || null,
1627
                options.segwit || null
1628
            )
1629
                .then(
1630
                    // result, deferred, self(apiclient)
1631
                    function(result) {
1632
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1633
1634
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1635
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1636
                        });
1637
1638
                        var wallet = new Wallet(
1639
                            self,
1640
                            options.identifier,
1641
                            Wallet.WALLET_VERSION_V3,
1642
                            null,
1643
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1644
                            options.storeDataOnServer ? options.encryptedSecret : null,
1645
                            {keyIndex: options.primaryPublicKey},
1646
                            options.backupPublicKey,
1647
                            blocktrailPublicKeys,
1648
                            keyIndex,
1649
                            result.segwit || 0,
1650
                            self.testnet,
1651
                            self.regtest,
1652
                            checksum,
1653
                            result.upgrade_key_index,
1654
                            options.useCashAddress,
1655
                            options.bypassNewAddressCheck
1656
                        );
1657
1658
                        // pass along decrypted data to avoid extra work
1659
                        return wallet.unlock({
1660
                            walletVersion: Wallet.WALLET_VERSION_V3,
1661
                            passphrase: options.passphrase,
1662
                            primarySeed: options.primarySeed,
1663
                            secret: options.secret
1664
                        }).then(function() {
1665
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1666
                            return [
1667
                                wallet,
1668
                                {
1669
                                    walletVersion: wallet.walletVersion,
1670
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1671
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1672
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1673
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1674
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1675
                                    blocktrailPublicKeys: blocktrailPublicKeys
1676
                                }
1677
                            ];
1678
                        });
1679
                    }
1680
                );
1681
        })
1682
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1683
1684
    return deferred.promise;
1685
};
1686
1687
function verifyPublicBip32Key(bip32Key, network) {
1688
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1689
    if (typeof hk.keyPair.d !== "undefined") {
1690
        throw new Error('BIP32Key contained private key material - abort');
1691
    }
1692
1693
    if (bip32Key[1].slice(0, 1) !== "M") {
1694
        throw new Error("BIP32Key contained non-public path - abort");
1695
    }
1696
}
1697
1698
function verifyPublicOnly(walletData, network) {
1699
    verifyPublicBip32Key(walletData.primary_public_key, network);
1700
    verifyPublicBip32Key(walletData.backup_public_key, network);
1701
}
1702
1703
/**
1704
 * create wallet using the API
1705
 *
1706
 * @param identifier            string      the wallet identifier to create
1707
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1708
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1709
 * @param primaryMnemonic       string      mnemonic to store
1710
 * @param checksum              string      checksum to store
1711
 * @param keyIndex              int         keyIndex that was used to create wallet
1712
 * @param segwit                bool
1713
 * @returns {q.Promise}
1714
 */
1715
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1716
                                                checksum, keyIndex, segwit) {
1717
    var self = this;
1718
1719
    var postData = {
1720
        identifier: identifier,
1721
        wallet_version: Wallet.WALLET_VERSION_V1,
1722
        primary_public_key: primaryPublicKey,
1723
        backup_public_key: backupPublicKey,
1724
        primary_mnemonic: primaryMnemonic,
1725
        checksum: checksum,
1726
        key_index: keyIndex,
1727
        segwit: segwit
1728
    };
1729
1730
    verifyPublicOnly(postData, self.network);
1731
1732
    return self.blocktrailClient.post("/wallet", null, postData);
1733
};
1734
1735
/**
1736
 * create wallet using the API
1737
 *
1738
 * @param identifier            string      the wallet identifier to create
1739
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1740
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1741
 * @param encryptedPrimarySeed  string      openssl format
1742
 * @param encryptedSecret       string      openssl format
1743
 * @param recoverySecret        string      openssl format
1744
 * @param checksum              string      checksum to store
1745
 * @param keyIndex              int         keyIndex that was used to create wallet
1746
 * @param supportSecret         string
1747
 * @param segwit                bool
1748
 * @returns {q.Promise}
1749
 */
1750
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1751
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1752
    var self = this;
1753
1754
    var postData = {
1755
        identifier: identifier,
1756
        wallet_version: Wallet.WALLET_VERSION_V2,
1757
        primary_public_key: primaryPublicKey,
1758
        backup_public_key: backupPublicKey,
1759
        encrypted_primary_seed: encryptedPrimarySeed,
1760
        encrypted_secret: encryptedSecret,
1761
        recovery_secret: recoverySecret,
1762
        checksum: checksum,
1763
        key_index: keyIndex,
1764
        support_secret: supportSecret || null,
1765
        segwit: segwit
1766
    };
1767
1768
    verifyPublicOnly(postData, self.network);
1769
1770
    return self.blocktrailClient.post("/wallet", null, postData);
1771
};
1772
1773
/**
1774
 * create wallet using the API
1775
 *
1776
 * @param identifier            string      the wallet identifier to create
1777
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1778
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1779
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1780
 * @param encryptedSecret       Buffer      buffer of ciphertext
1781
 * @param recoverySecret        Buffer      buffer of recovery secret
1782
 * @param checksum              string      checksum to store
1783
 * @param keyIndex              int         keyIndex that was used to create wallet
1784
 * @param supportSecret         string
1785
 * @param segwit                bool
1786
 * @returns {q.Promise}
1787
 */
1788
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1789
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1790
    var self = this;
1791
1792
    var postData = {
1793
        identifier: identifier,
1794
        wallet_version: Wallet.WALLET_VERSION_V3,
1795
        primary_public_key: primaryPublicKey,
1796
        backup_public_key: backupPublicKey,
1797
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1798
        encrypted_secret: encryptedSecret.toString('base64'),
1799
        recovery_secret: recoverySecret.toString('hex'),
1800
        checksum: checksum,
1801
        key_index: keyIndex,
1802
        support_secret: supportSecret || null,
1803
        segwit: segwit
1804
    };
1805
1806
    verifyPublicOnly(postData, self.network);
1807
1808
    return self.blocktrailClient.post("/wallet", null, postData);
1809
};
1810
1811
/**
1812
 * create wallet using the API
1813
 *
1814
 * @param identifier            string      the wallet identifier to create
1815
 * @param postData              object
1816
 * @param [cb]                  function    callback(err, result)
1817
 * @returns {q.Promise}
1818
 */
1819
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1820
    var self = this;
1821
1822
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1823
};
1824
1825
/**
1826
 * upgrade wallet to use a new account number
1827
 *  the account number specifies which blocktrail cosigning key is used
1828
 *
1829
 * @param identifier            string      the wallet identifier
1830
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1831
 * @param keyIndex              int         keyIndex that was used to create wallet
1832
 * @param [cb]                  function    callback(err, result)
1833
 * @returns {q.Promise}
1834
 */
1835
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1836
    var self = this;
1837
1838
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1839
        key_index: keyIndex,
1840
        primary_public_key: primaryPublicKey
1841
    }, cb);
1842
};
1843
1844
/**
1845
 * get the balance for the wallet
1846
 *
1847
 * @param identifier            string      the wallet identifier
1848
 * @param [cb]                  function    callback(err, result)
1849
 * @returns {q.Promise}
1850
 */
1851
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1852
    var self = this;
1853
1854
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1855
};
1856
1857
/**
1858
 * do HD wallet discovery for the wallet
1859
 *
1860
 * @param identifier            string      the wallet identifier
1861
 * @param [cb]                  function    callback(err, result)
1862
 * @returns {q.Promise}
1863
 */
1864
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1865
    var self = this;
1866
1867
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1868
};
1869
1870
1871
/**
1872
 * get a new derivation number for specified parent path
1873
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1874
 *
1875
 * @param identifier            string      the wallet identifier
1876
 * @param path                  string      the parent path for which to get a new derivation,
1877
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1878
 * @param [cb]                  function    callback(err, result)
1879
 * @returns {q.Promise}
1880
 */
1881
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1882
    var self = this;
1883
1884
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1885
};
1886
1887
1888
/**
1889
 * delete the wallet
1890
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1891
 *  is required to be able to delete a wallet
1892
 *
1893
 * @param identifier            string      the wallet identifier
1894
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1895
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1896
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1897
 * @param [cb]                  function    callback(err, result)
1898
 * @returns {q.Promise}
1899
 */
1900
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1901
    var self = this;
1902
1903
    if (typeof force === "function") {
1904
        cb = force;
1905
        force = false;
1906
    }
1907
1908
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1909
        checksum: checksumAddress,
1910
        signature: checksumSignature
1911
    }, cb);
1912
};
1913
1914
/**
1915
 * use the API to get the best inputs to use based on the outputs
1916
 *
1917
 * the return array has the following format:
1918
 * [
1919
 *  "utxos" => [
1920
 *      [
1921
 *          "hash" => "<txHash>",
1922
 *          "idx" => "<index of the output of that <txHash>",
1923
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1924
 *          "value" => 32746327,
1925
 *          "address" => "1address",
1926
 *          "path" => "m/44'/1'/0'/0/13",
1927
 *          "redeem_script" => "<redeemScript-hex>",
1928
 *      ],
1929
 *  ],
1930
 *  "fee"   => 10000,
1931
 *  "change"=> 1010109201,
1932
 * ]
1933
 *
1934
 * @param identifier        string      the wallet identifier
1935
 * @param pay               array       {'address': (int)value}     coins to send
1936
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1937
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1938
 * @param feeStrategy       string      defaults to
1939
 * @param options
1940
 * @param [cb]              function    callback(err, utxos, fee, change)
1941
 * @returns {q.Promise}
1942
 */
1943
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1944
    var self = this;
1945
1946
    if (typeof feeStrategy === "function") {
1947
        cb = feeStrategy;
1948
        feeStrategy = null;
1949
        options = {};
1950
    } else if (typeof options === "function") {
1951
        cb = options;
1952
        options = {};
1953
    }
1954
1955
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1956
    options = options || {};
1957
1958
    var deferred = q.defer();
1959
    deferred.promise.spreadNodeify(cb);
1960
1961
    var params = {
1962
        lock: lockUTXO,
1963
        zeroconf: allowZeroConf ? 1 : 0,
1964
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1965
        fee_strategy: feeStrategy
1966
    };
1967
1968
    if (options.forcefee) {
1969
        params['forcefee'] = options.forcefee;
1970
    }
1971
1972
    deferred.resolve(
1973
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1974
            function(result) {
1975
                return [result.utxos, result.fee, result.change, result];
1976
            },
1977
            function(err) {
1978
                if (err.message.match(/too low to pay the fee/)) {
1979
                    throw blocktrail.WalletFeeError(err);
1980
                }
1981
1982
                throw err;
1983
            }
1984
        )
1985
    );
1986
1987
    return deferred.promise;
1988
};
1989
1990
/**
1991
 * @param [cb]              function    callback(err, utxos, fee, change)
1992
 * @returns {q.Promise}
1993
 */
1994
APIClient.prototype.feePerKB = function(cb) {
1995
    var self = this;
1996
1997
    var deferred = q.defer();
1998
    deferred.promise.spreadNodeify(cb);
1999
2000
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
2001
2002
    return deferred.promise;
2003
};
2004
2005
/**
2006
 * send the transaction using the API
2007
 *
2008
 * @param identifier        string      the wallet identifier
2009
 * @param txHex             string      partially signed transaction as hex string
2010
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2011
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2012
 * @param [twoFactorToken]  string      2FA token
2013
 * @param [prioboost]       bool
2014
 * @param [cb]              function    callback(err, txHash)
2015
 * @returns {q.Promise}
2016
 */
2017
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2018
    var self = this;
2019
2020
    if (typeof twoFactorToken === "function") {
2021
        cb = twoFactorToken;
2022
        twoFactorToken = null;
2023
        prioboost = false;
2024
    } else if (typeof prioboost === "function") {
2025
        cb = prioboost;
2026
        prioboost = false;
2027
    }
2028
2029
    var data = {
2030
        paths: paths,
2031
        two_factor_token: twoFactorToken
2032
    };
2033
    if (typeof txHex === "string") {
2034
        data.raw_transaction = txHex;
2035
    } else if (typeof txHex === "object") {
2036
        Object.keys(txHex).map(function(key) {
2037
            data[key] = txHex[key];
2038
        });
2039
    }
2040
2041
    return self.blocktrailClient.post(
2042
        "/wallet/" + identifier + "/send",
2043
        {
2044
            check_fee: checkFee ? 1 : 0,
2045
            prioboost: prioboost ? 1 : 0
2046
        },
2047
        data,
2048
        cb
2049
    );
2050
};
2051
2052
/**
2053
 * setup a webhook for this wallet
2054
 *
2055
 * @param identifier        string      the wallet identifier
2056
 * @param webhookIdentifier string      identifier for the webhook
2057
 * @param url               string      URL to receive webhook events
2058
 * @param [cb]              function    callback(err, webhook)
2059
 * @returns {q.Promise}
2060
 */
2061
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2062
    var self = this;
2063
2064
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2065
};
2066
2067
/**
2068
 * delete a webhook that was created for this wallet
2069
 *
2070
 * @param identifier        string      the wallet identifier
2071
 * @param webhookIdentifier string      identifier for the webhook
2072
 * @param [cb]              function    callback(err, success)
2073
 * @returns {q.Promise}
2074
 */
2075
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2076
    var self = this;
2077
2078
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2079
};
2080
2081
/**
2082
 * get all transactions for an wallet (paginated)
2083
 *
2084
 * @param identifier    string      wallet identifier
2085
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2086
 * @param [cb]          function    callback function to call when request is complete
2087
 * @return q.Promise
2088
 */
2089
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2090
    var self = this;
2091
2092
    if (typeof params === "function") {
2093
        cb = params;
2094
        params = null;
2095
    }
2096
2097
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2098
};
2099
2100
/**
2101
 * get all addresses for an wallet (paginated)
2102
 *
2103
 * @param identifier    string      wallet identifier
2104
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2105
 * @param [cb]          function    callback function to call when request is complete
2106
 * @return q.Promise
2107
 */
2108
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2109
    var self = this;
2110
2111
    if (typeof params === "function") {
2112
        cb = params;
2113
        params = null;
2114
    }
2115
2116
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2117
};
2118
2119
/**
2120
 * @param identifier    string      wallet identifier
2121
 * @param address       string      the address to label
2122
 * @param label         string      the label
2123
 * @param [cb]          function    callback(err, res)
2124
 * @return q.Promise
2125
 */
2126
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2127
    var self = this;
2128
2129
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2130
};
2131
2132
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2133
    var self = this;
2134
2135
    if (typeof feeStrategy === "function") {
2136
        cb = feeStrategy;
2137
        feeStrategy = null;
2138
    } else if (typeof options === "function") {
2139
        cb = options;
2140
        options = {};
2141
    }
2142
2143
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2144
    options = options || {};
2145
2146
    var params = {
2147
        outputs: options.outputs ? options.outputs : 1,
2148
        zeroconf: allowZeroConf ? 1 : 0,
2149
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2150
        fee_strategy: feeStrategy
2151
    };
2152
2153
    if (options.forcefee) {
2154
        params['forcefee'] = options.forcefee;
2155
    }
2156
2157
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2158
};
2159
2160
/**
2161
 * get all UTXOs for an wallet (paginated)
2162
 *
2163
 * @param identifier    string      wallet identifier
2164
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2165
 * @param [cb]          function    callback function to call when request is complete
2166
 * @return q.Promise
2167
 */
2168
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2169
    var self = this;
2170
2171
    if (typeof params === "function") {
2172
        cb = params;
2173
        params = null;
2174
    }
2175
2176
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2177
};
2178
2179
/**
2180
 * get a paginated list of all wallets associated with the api user
2181
 *
2182
 * @param [params]      object      pagination: {page: 1, limit: 20}
2183
 * @param [cb]          function    callback function to call when request is complete
2184
 * @return q.Promise
2185
 */
2186
APIClient.prototype.allWallets = function(params, cb) {
2187
    var self = this;
2188
2189
    if (typeof params === "function") {
2190
        cb = params;
2191
        params = null;
2192
    }
2193
2194
    return self.blocktrailClient.get("/wallets", params, true, cb);
2195
};
2196
2197
/**
2198
 * verify a message signed bitcoin-core style
2199
 *
2200
 * @param message        string
2201
 * @param address        string
2202
 * @param signature      string
2203
 * @param [cb]          function    callback function to call when request is complete
2204
 * @return q.Promise
2205
 */
2206
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2207
    var self = this;
2208
2209
    var deferred = q.defer();
2210
    deferred.promise.nodeify(cb);
2211
    try {
2212
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2213
        deferred.resolve(result);
2214
    } catch (e) {
2215
        deferred.reject(e);
2216
    }
2217
2218
    return deferred.promise;
2219
};
2220
2221
/**
2222
 * max is 0.001
2223
 * testnet only
2224
 *
2225
 * @param address
2226
 * @param amount
2227
 * @param cb
2228
 */
2229
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2230
    var self = this;
2231
2232
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2233
};
2234
2235
/**
2236
 * send a raw transaction
2237
 *
2238
 * @param rawTransaction    string      raw transaction as HEX
2239
 * @param [cb]              function    callback function to call when request is complete
2240
 * @return q.Promise
2241
 */
2242
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2243
    var self = this;
2244
2245
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2246
};
2247
2248
/**
2249
 * get the current price index
2250
 *
2251
 * @param [cb]          function    callback({'USD': 287.30})
2252
 * @return q.Promise
2253
 */
2254
APIClient.prototype.price = function(cb) {
2255
    var self = this;
2256
2257
    return self.blocktrailClient.get("/price", null, false, cb);
2258
};
2259
2260
module.exports = APIClient;
2261